package com.xuan.system.service.contest.impl;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.domain.TableDataInfo;
import com.xuan.common.core.enums.ResultCode;
import com.xuan.common.core.exception.BusinessException;
import com.xuan.common.redis.service.RedisService;
import com.xuan.system.domain.contest.dto.*;
import com.xuan.system.domain.contest.entity.Contest;
import com.xuan.system.domain.contest.vo.ContestDetailVo;
import com.xuan.system.domain.contest.vo.ContestVo;
import com.xuan.system.domain.contestQuestion.entity.ContestQuestion;
import com.xuan.system.domain.question.entity.Question;
import com.xuan.system.domain.question.vo.QuestionVo;
import com.xuan.system.manager.ContestCacheManager;
import com.xuan.system.mapper.ContestQuestionMapper;
import com.xuan.system.mapper.QuestionMapper;
import com.xuan.system.service.contestQuestion.ContestQuestionService;
import com.xuan.system.service.contest.ContestService;
import com.xuan.system.mapper.ContestMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/**
 * @author ASUS
 * @description 针对表【tb_contest(竞赛表)】的数据库操作Service实现
 * @createDate 2024-11-16 22:40:37
 */
@Service
@Slf4j
public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest>
        implements ContestService {

    @Resource
    private ContestMapper contestMapper;

    @Resource
    private ContestQuestionService contestQuestionService;

    @Resource
    private ContestQuestionMapper contestQuestionMapper;

    @Autowired
    private QuestionMapper questionMapper;
    @Autowired
    private ContestCacheManager contestCacheManager;



    @Override
    public TableDataInfo listContest(ContestQueryDTO contestQueryDTO) {
        PageHelper.startPage(contestQueryDTO.getPageNum(), contestQueryDTO.getPageSize());
        List<ContestVo> contestVoList = contestMapper.listContest(contestQueryDTO);
        return TableDataInfo.success(contestVoList);
    }

    @Override
    public String addContest(ContestAddDTO contestAddDTO) {
        // 1. 进行参数校验
        checkContestArgs(contestAddDTO, null);
        // 2. 新增竞赛记录
        Contest contest = new Contest();
        BeanUtils.copyProperties(contestAddDTO, contest);
        int insert = contestMapper.insert(contest);
        if (insert != 1) {
            log.error("新增 竞赛 记录失败");
            throw new BusinessException("新增竞赛失败");
        }
        return contest.getContestId().toString();
    }

    @Override
    @Transactional
    public boolean deleteContest(Long contestId) {
        // 1. 查看竞赛是否存在
        Contest contest = getContest(contestId);
        // 2. 删除数据库数据(竞赛信息,以及 竞赛-题目 关系信息)
        // 1) 竞赛已开始/结束 or 为已发布的状态则不能删除
        checkContestIsOperable(contest);
        int res = contestMapper.deleteById(contestId);
        if (res != 1) {
            log.error("数据库,竞赛记录删除失败!");
            throw new BusinessException("竞赛删除失败");
        }
        // 2) 删除 竞赛-题目 关系记录
        contestQuestionMapper.delete(new LambdaQueryWrapper<ContestQuestion>().
                eq(ContestQuestion::getContestId, contestId));
        return true;
    }




    @Override
    public boolean editContest(ContestEditDTO contestEditDTO) {
        Long contestId = contestEditDTO.getContestId();
        // 1. 先查看竞赛是否存在
        Contest contest = getContest(contestId);
        // 2. 1)竞赛进行中或结束后都不可编辑  2) 已发布的竞赛不能编辑
        checkContestIsOperable(contest);
        // 3. 检查更新后的参数是否合法
        checkContestArgs(contestEditDTO, contestId);
        // 4. 更新数据库数据(竞赛表)
        //    竞赛的基本信息:名称,开始和结束时间
        BeanUtils.copyProperties(contestEditDTO, contest);
        int res = contestMapper.updateById(contest);
        if (res != 1) {
            log.error("更新竞赛表失败");
            throw new BusinessException("竞赛信息更新失败");
        }
        return true;
    }

    @Override
    public ContestDetailVo getContestDetail(Long contestId) {
        ContestDetailVo contestDetailVo = new ContestDetailVo();
        // 1. 先检查竞赛是否存在
        Contest contest = getContest(contestId);
        BeanUtils.copyProperties(contest, contestDetailVo);
        // 2. 查询 竞赛-题目 关系记录
        List<ContestQuestion> contestQuestionList = contestQuestionMapper.selectList(new QueryWrapper<ContestQuestion>()
                // 查询题目 id 和题目顺序
                .select("question_id", "question_order")
                .eq("contest_id", contestId));
        if (CollectionUtils.isEmpty(contestQuestionList)) {
            // 如果没有添加题目直接返回结果即可
            contestDetailVo.setContestQuestionList(new ArrayList<>());
            return contestDetailVo;
        }

        // 3. 根据关系记录查询对应题目信息
        List<Long> questionIdList = contestQuestionList.stream().map(ContestQuestion::getQuestionId).toList();
        List<Question> questionList = questionMapper.selectList(new QueryWrapper<Question>()
                .select("question_id", "title", "difficulty")
                .in("question_id", questionIdList));
        // 转换为 Vo 对象
        List<QuestionVo> questionVoList = BeanUtil.copyToList(questionList, QuestionVo.class);
        contestDetailVo.setContestQuestionList(questionVoList);
        return contestDetailVo;
    }

    @Override
    public boolean addContestQuestion(ContestQuestionAddDTO contestQuestionDTO) {
        Long contestId = contestQuestionDTO.getContestId();
        Set<Long> questionIdSet = contestQuestionDTO.getQuestionIdSet();
        // 1. 先判断竞赛是否存在
        Contest contest = getContest(contestId);
        // 2. 竞赛未开赛才可添加(startTime > now() )
        //    竞赛进行中或结束后都不可添加
        checkContestIsOperable(contest);
        // 没有勾选题目直接返回即可
        if (CollectionUtils.isEmpty(questionIdSet)) {
            return true;
        }

        // 3. 判断所有要添加的题目是否存在
        Long count = questionMapper.selectCount(new QueryWrapper<Question>().in("question_id", questionIdSet));
        if (count == null || count != questionIdSet.size()) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "部分题目不存在,请检查后重新提交");
        }
        // 4. 直接一次性批量插入
        // TODO   此处有 BUG, 二次新增题目时,会导致题目顺序发生重复
        int num = 1;
        List<ContestQuestion> contestQuestionList = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            ContestQuestion contestQuestion = new ContestQuestion();
            contestQuestion.setContestId(contestId);
            contestQuestion.setQuestionId(questionId);
            contestQuestion.setQuestionOrder(num++);
            contestQuestionList.add(contestQuestion);
        }
        return contestQuestionService.saveBatch(contestQuestionList);
    }

    @Override
    public boolean deleteContestQuestion(Long contestId, Long questionId) {
        // 1. 先查看竞赛是否存在
        Contest contest = getContest(contestId);
        // 2. 竞赛未开赛才可删除(startTime > now() )
        //    竞赛进行中或结束后都不可删除
        checkContestIsOperable(contest);
        // 3. 直接删除即可(根据删除结果可以知道 竞赛-题目 是否存在)
        int res = contestQuestionMapper.delete(new QueryWrapper<ContestQuestion>()
                .eq("contest_id", contestId)
                .eq("question_id", questionId));
        if (res != 1) {
            throw new BusinessException("题目不在竞赛中,删除失败");
        }
        return true;
    }

    @Override
    public long publishContest(Long contestId) {
        // 1. 先查看竞赛是否存在
        Contest contest = getContest(contestId);
        // 2. 竞赛开始/结束后不能发布
        if (LocalDateTime.now().isAfter(contest.getStartTime())) {
            throw new BusinessException("竞赛发布时间应早于开始时间");
        }
        // 3. 竞赛存在题目才可发布
        Long count = contestQuestionMapper.selectCount(new LambdaQueryWrapper<ContestQuestion>()
                .eq(ContestQuestion::getContestId, contestId));
        if (count == null || count < 1) {
            throw new BusinessException("当前竞赛不包含题目,不允许发布");
        }
        contest.setStatus(Constants.TRUE);
        int result = contestMapper.updateById(contest);
        if (result < 1) return result;
        // 4. 将当前发布的竞赛存储到 redis 中
        contestCacheManager.addCache(contest);
        return result;
    }

    @Override
    public int cancelPublish(Long contestId) {
        Contest contest = getContest(contestId);
        checkContest(contest);
        contest.setStatus(Constants.FALSE);
        int result = contestMapper.updateById(contest);
        if (result < 1) return result;
        // 将 当前竞赛信息 和 竞赛题目列表信息 从 redis 中删除
        contestCacheManager.deleteCache(contest);
        return result;
    }


    private void checkContest(Contest contest) {
        if (LocalDateTime.now().isAfter(contest.getStartTime())) {
            throw new BusinessException("竞赛已开始/结束,不能修改竞赛信息");
        }
    }


    private void checkContestIsOperable(Contest contest) {
        if (Objects.equals(contest.getStatus(), Constants.TRUE)) {
            throw new BusinessException("已发布的竞赛不能进行操作");
        }
        if (LocalDateTime.now().isAfter(contest.getStartTime())) {
            throw new BusinessException("竞赛已开始/结束,不能进行该操作");
        }
    }

    private Contest getContest(Long contestId) {
        Contest contest = contestMapper.selectOne(new LambdaQueryWrapper<Contest>()
                    // 查出 竞赛id,标题,开始和结束时间即可
                    .select(Contest::getContestId, Contest::getStartTime, Contest::getEndTime, Contest::getTitle)
                    .eq(Contest::getContestId, contestId));
        if (contest == null) {
            throw new BusinessException(ResultCode.FAILED_NOT_EXISTS, "竞赛不存在");
        }
        return contest;
    }


    private void checkContestArgs(ContestAddDTO contestSaveDTO, Long contestId) {
        // 1. 参数不能为空
        String title = contestSaveDTO.getTitle();
        LocalDateTime startTime = contestSaveDTO.getStartTime();
        LocalDateTime endTime = contestSaveDTO.getEndTime();
        if (StringUtils.isBlank(title)) {
            throw new BusinessException("参数不能为空");
        } else if (title.length() > 50) {
            throw new BusinessException("竞赛名称过长,应小于 50 ");
        }
        if (startTime == null || endTime == null) {
            throw new BusinessException("未设置开始或结束时间");
        }
        // 2. 竞赛名称不能重复
        // 1) 若是新增竞赛,则不能重复; 2) 若是编辑竞赛,则可以不修改标题
        QueryWrapper<Contest> queryWrapper = new QueryWrapper<Contest>().eq("title", title);
        // contestId != null 则是编辑竞赛页面,因此
        // 1. title 可以不发生变化 2. 发生变化后不能与其他竞赛的 title 相同
        queryWrapper.ne(contestId != null, "contest_id", contestId);

        Long count = contestMapper.selectCount(queryWrapper);
        if (count == null || count > 0) {
            throw new BusinessException("竞赛名称不能重复");
        }
        // 该系统中可以允许题目为空,但是这种竞赛不能发布!!!
//        if (CollectionUtils.isEmpty(questionIdList)) {
//            throw new BusinessException("每个竞赛至少要包含一道题目");
//        }
        // 3. 开始时间和结束时间要合理
        if (startTime.isBefore(LocalDateTime.now())) {
            throw new BusinessException("竞赛开始时间必须大于当前时间");
        }
        if (startTime.isAfter(endTime)) {
            throw new BusinessException("竞赛结束时间必须在开始时间之后");
        }
    }

    private boolean batchInsertContestQuestion(List<Long> questionIdList, Long contestId) {
        List<ContestQuestion> contestQuestionList = new ArrayList<>();
        for (int i = 1; i <= questionIdList.size(); i++) {
            Long questionId = questionIdList.get(i - 1);
            ContestQuestion contestQuestion = new ContestQuestion();
            contestQuestion.setContestId(contestId);
            contestQuestion.setQuestionId(questionId);
            contestQuestion.setQuestionOrder(i);
            contestQuestionList.add(contestQuestion);
        }
        return contestQuestionService.saveBatch(contestQuestionList);
    }
}




